﻿(*  
    F# Dataflow for ViewModels
    (c) Andras Janko, 2013, Available under Microsoft Public License.
*)

namespace FsFlow

open System
open System.Collections
open System.Collections.Generic
open System.Collections.Specialized
open System.ComponentModel
open System.Windows.Input

type Variable<'a>(initVal: 'a) =
    let mutable value = initVal
    let mutable dependents = Set.empty

    interface IVariable<'a> with 
        member this.Value
            with get() = value
            and  set v =
                if value != v then
                    value <- v
                    for (p: IVariableP) in dependents do p.Trigger this
                    Core.refresh()

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p
        
type Prop<'a>(getValue: unit -> 'a, setValue: 'a -> unit) =
    let mutable dependents = Set.empty

    interface IVariable<'a> with 
        member this.Value
            with get() = getValue()
            and  set v =
                if getValue() != v then
                    setValue v
                    for (p: IVariableP) in dependents do p.Trigger this
                    Core.refresh()

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p
        
type Mapped<'a, 'b>(source: IVariable<'a>, mapping: 'a -> 'b) as this =
    let mutable value = mapping !source
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    do  source.AddDependent this

    member private this.SetValue v =
        value <- v
        for (p: IVariableP) in dependents do p.Trigger this
        Core.refresh()

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IVariableP<'b> with
        member this.Trigger _ =
            let newValue = mapping !source
            if value != newValue then
                this.SetValue newValue

        member this.Ticket = ticket
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Dispose() = source.RemoveDependent this
        member this.Value
            with get() = value
            and  set v = if value != v then this.SetValue v

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p

type LazyMapped<'a, 'b>(source: IVariable<'a>, mapping: 'a -> 'b) as this =
    let mutable value = Unchecked.defaultof<'b>
    let mutable valueExpired = true
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    do  source.AddDependent this  

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IVariableP<'b> with
        member this.Trigger _ =
            if not valueExpired then
                valueExpired <- true
                for (p: IVariableP) in dependents do p.Trigger this
                Core.refresh()
        member this.Ticket = ticket
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Dispose() = source.RemoveDependent this
        member this.Value
            with get() =
                if valueExpired then
                    value <- mapping !source
                    valueExpired <- false
                value
            and  set v =
                if value != v then
                    value <- v
                    valueExpired <- false
                    for (p: IVariableP) in dependents do p.Trigger this
        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p

type MappedAction<'a>(source: IVariable<'a>, action: 'a -> unit) as this =
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    do  source.AddDependent this

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IVariableP with
        member this.Trigger _ = action !source
        member this.Ticket = ticket
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Dispose() = source.RemoveDependent this

type VariablePath<'a, 'b>(source: IVariable<'a>, path: 'a -> IVariable<'b>) =
    let mutable valueSource = path !source
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    member private this.Trigger() =
        for (p: IVariableP) in dependents do p.Trigger this
        Core.refresh()

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    member this.Initialize() =
        source.AddDependent this
        valueSource.AddDependent this    

    interface IVariableP<'b> with
        member this.Trigger s =
            if s === source then
                let oldValue = !valueSource
                valueSource.RemoveDependent this
                valueSource <- path !source
                valueSource.AddDependent this
                if oldValue != !valueSource then
                    this.Trigger()
            else this.Trigger()                    

        member this.Ticket = ticket
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Dispose() = source.RemoveDependent this
        member this.Value
            with get() = !valueSource
            and  set v = valueSource := v

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p

type VariablePathOpt<'a, 'b>(source: IVariable<'a>, path: 'a -> IVariable<'b> option) =
    let mutable valueSource = path !source
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    member private this.Trigger() =
        for (p: IVariableP) in dependents do p.Trigger this
        Core.refresh()

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    member this.Initialize() =
        source.AddDependent this
        match valueSource with Some vs -> vs.AddDependent this | None -> ()

    interface IVariableP<'b option> with
        member this.Trigger s =
           
            if s === source then
                let oldValue = !valueSource
                match valueSource with Some vs -> vs.RemoveDependent this | None -> ()
                valueSource <- path !source
                match valueSource with Some vs -> vs.AddDependent this | None -> ()
                if oldValue != !valueSource then
                    this.Trigger()
            else this.Trigger()                    

        member this.Ticket = ticket
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Dispose() = source.RemoveDependent this
        member this.Value
            with get() = valueSource |> Option.map (!)
            and  set v =
                match valueSource with
                | Some vs -> 
                    match v with
                    | Some vv -> vs := vv
                    | None ->
                        valueSource <- None
                        this.Trigger()
                | None -> ()

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p

type VariableTuple<'a, 'b>(source1: IVariable<'a>, source2: IVariable<'b>) =
    interface IVariable<'a * 'b> with
        member this.AddDependent p =
            source1.AddDependent p
            source2.AddDependent p
        member this.RemoveDependent p =
            source1.RemoveDependent p
            source2.RemoveDependent p
        member this.Value
            with get() = !source1, !source2
            and set (v1, v2) = 
                source1 := v1
                source2 := v2

type VariableTuple<'a, 'b, 'c>(source1: IVariable<'a>, source2: IVariable<'b>, source3: IVariable<'c>) =
    interface IVariable<'a * 'b * 'c> with
        member this.AddDependent p =
            source1.AddDependent p
            source2.AddDependent p
            source3.AddDependent p
        member this.RemoveDependent p =
            source1.RemoveDependent p
            source2.RemoveDependent p
            source3.RemoveDependent p
        member this.Value
            with get() = !source1, !source2, !source3
            and set (v1, v2, v3) = 
                source1 := v1
                source2 := v2
                source3 := v3

type PathBody<'a> =
    | CompPath of IPath<'a>
    | CompRes of 'a
    | CompZero

type private Path<'a, 'b>(source: IVariable<'a>, body: 'a -> 'b PathBody) =
    let mutable valueSource = body !source
    let mutable ticket = Ticket.zero
    let mutable compHead = None 
    let mutable needsRefresh = false
   
    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IPath<'b> with
        member this.Initialize(t, ch) =
            ticket <- t
            compHead <- ch
            match valueSource with CompPath vs -> vs.Initialize(t |> Ticket.compNextDepth, ch) | _ -> ()
            source.AddDependent this

        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket
        member this.Ticket = ticket
        member this.Value =
            match valueSource with
            | CompPath p -> !p  
            | CompRes v -> v
            | CompZero -> invalidOp ""

        member this.Trigger s =
            if not needsRefresh then 
                needsRefresh <- true
                Core.addToRefresh this

        member this.Refresh() =
            needsRefresh <- false
            match valueSource with
            | CompPath p -> dispose p
            | _ -> ()
            valueSource <- body !source
            match valueSource with
            | CompPath p ->
                p.Initialize(ticket |> Ticket.compNextDepth, compHead)
                !compHead := !p
            | CompRes v -> !compHead := v
            | _ -> ()
 
        member this.Dispose() =
            match valueSource with
            | CompPath p -> dispose p
            | _ -> ()
            source.RemoveDependent this
            Core.removeFromRefresh this

type Comp<'a>(path: IPath<'a>) =
    inherit Variable<'a>(!path)
    
    interface IVariableD<'a> with
        member this.Dispose() = dispose path    

type CompBuilder(level) =
    let getTicket = Core.getCompTicket level

    member this.Bind(source: #IVariable<_>, body: _ -> PathBody<_>) =
        new Path<_,_>(source, body) :> IPath<_> |> CompPath

    member this.Bind((source1: #IVariable<_>, source2: #IVariable<_>), body: _ -> PathBody<_>) =
        new Path<_,_>(VariableTuple<_,_>(source1, source2), body) :> IPath<_> |> CompPath

    member this.Bind((source1: #IVariable<_>, source2: #IVariable<_>, source3: #IVariable<_>), body: _ -> PathBody<_>) =
        new Path<_,_>(VariableTuple<_,_,_>(source1, source2, source3), body) :> IPath<_> |> CompPath

    member this.Return(body) = CompRes body

    member this.ReturnFrom(source: #IVariable<_>) =
        new Path<_,_>(source, CompRes) :> IPath<_> |> CompPath 
   
    member this.Zero() = CompZero

    member this.Run(body: PathBody<_>) = 
        match body with
        | CompPath p -> new Comp<_>(p) :> IVariableD<_> |>! fun c -> p.Initialize(getTicket(), Some (upcast c))
        | _ -> invalidOp ""

type Published<'a>(source: IVariable<'a>) as this =
    let propertyChanged = Event<_,_>()
    let ticket = Core.getMappedTicket()

    do  source.AddDependent this

    abstract SetValue: 'a -> unit
    override this.SetValue(v) = source := v

    abstract Trigger: unit -> unit
    override this.Trigger() = propertyChanged.Trigger(this, Core.propertiesChanged)

    member this.Value
        with get() = !source
        and  set v =
            this.SetValue(v)
            this.Trigger()
    
    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member this.PropertyChanged = propertyChanged.Publish

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IVariableP<'a> with
        member this.Trigger _ = this.Trigger() 
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() = source.RemoveDependent this

        member this.Value
            with get() = !source
            and  set v =
                this.SetValue(v)
                this.Trigger()

        member this.AddDependent p = source.AddDependent p
        member this.RemoveDependent p = source.RemoveDependent p

type PublishedOpt<'a>(source: IVariable<'a option>) =
    inherit Published<'a option>(source)

    member this.ValueNull
        with get() = Null.ofOption !source
        and  set v = this.SetValue(Option.ofNull v)

type PublishedNull<'a when 'a: (new: unit -> 'a) and 'a: struct and 'a :> ValueType>(source: IVariable<'a option>) =
    inherit Published<'a option>(source)

    member this.ValueNull
        with get() = Nullable.ofOption !source
        and  set v = this.SetValue(Option.ofNullable v)

type PublishedEnum<'a when 'a: (new: unit->'a) and 'a: struct and 'a :> ValueType>(source: IVariable<'a>) =
    inherit Published<'a>(source)

    member this.Item
        with get name = name = Enum.GetName(typeof<'a>, !source)   
        and  set name v =
            if v then
                match Enum.TryParse name with
                | true, e -> this.SetValue(e)
                | _ -> ()                                          

    member this.Names = Enum.GetNames(typeof<'a>)                                        

type PublishedConst<'a>(value: 'a) =
    let propertyChanged = Event<_,_>()

    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member this.PropertyChanged = propertyChanged.Publish

    override this.Equals o = this === o
    override this.GetHashCode() = 0

    member this.Value
        with get() = value
        and  set (v: 'a) = propertyChanged.Trigger(this, Core.propertiesChanged)

    interface IVariableP<'a> with 
        member this.Trigger _ = propertyChanged.Trigger(this, Core.propertiesChanged)
        member this.CompareTo _ = 0
        member this.Ticket = Ticket.zero
        member this.Dispose() = ()
        member this.Value
            with get() = value
            and  set v = propertyChanged.Trigger(this, Core.propertiesChanged)
        member this.AddDependent d = ()
        member this.RemoveDependent d = ()

type PublishedCommand(state: IVariable<CommandState>, defaultName, execute) as this =
    inherit Published<CommandState>(state)
    
    let canExecuteChanged = Event<_,_>()

    do  state.AddDependent this
    
    member this.Name = match !state with EnabledNamed n -> n | _ -> defaultName
    member this.Error = match !state  with ErrorMessage e -> e | _ -> null
           
    override this.Trigger() =
        canExecuteChanged.Trigger (this, EventArgs.Empty)
        base.Trigger()

    interface ICommandP with 
        member this.CanExecute _ = match !state with Enabled | EnabledNamed _ -> true | _ -> false        
        member this.Execute _ = execute()
        [<CLIEvent>] 
        member this.CanExecuteChanged = canExecuteChanged.Publish
        member this.Name = this.Name
        member this.Error = this.Error

type EnabledCommand(name, execute) =
    let canExecuteChanged = Event<_,_>()
    
    member this.Name = name
    member this.Error = null

    interface ICommand with 
        member this.CanExecute _ = true
        member this.Execute _ = execute()
        [<CLIEvent>] 
        member this.CanExecuteChanged = canExecuteChanged.Publish
//
//// Collections
//
type VariableSet<'a when 'a: comparison>(initSet: 'a Set) =
    let mutable value = initSet
    let mutable dependents = Set.empty<IVariableP>
    let mutable dependentsSet = Set.empty<IVariableSetP<'a>>

    interface IEnumerable with
        member this.GetEnumerator() = (value :> IEnumerable).GetEnumerator()

    interface IVariableSet<'a> with
        member this.Value
            with get() = value
            and  set v = 
                let ov = value
                if ov <> v then
                    value <- v
                    let change = ov - v, v - ov
                    for ps in dependentsSet do ps.TriggerChange this change
                    for p in dependents do p.Trigger this

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p
        member this.AddDependentSet ps = dependentsSet <- dependentsSet.Add ps
        member this.RemoveDependentSet ps = dependentsSet <- dependentsSet.Remove ps
        
        member this.Add v =
            if not <| value.Contains v then
                value <- value.Add v
                for ps in dependentsSet do ps.TriggerAdd this v
                for p in dependents do p.Trigger this

        member this.Remove v =
            if value.Contains v then
                value <- value.Remove v
                for ps in dependentsSet do ps.TriggerRemove this v
                for p in dependents do p.Trigger this
                true
            else false

        member this.Clear() =
            if not value.IsEmpty then
                value <- Set.empty
                for ps in dependentsSet do ps.TriggerClear this
                for p in dependents do p.Trigger this

        member this.Change(removed, added) =
            value <- value - removed + added
            match removed.Count, added.Count with
            | 0, 0 -> ()
            | 1, 0 ->
                for ps in dependentsSet do ps.TriggerRemove this removed.MinimumElement
                for p in dependents do p.Trigger this
            | 0, 1 ->
                for ps in dependentsSet do ps.TriggerAdd this added.MinimumElement
                for p in dependents do p.Trigger this
            | _ ->
                for ps in dependentsSet do ps.TriggerChange this (removed, added)
                for p in dependents do p.Trigger this
        
        member this.Contains(v) = value.Contains v
        member this.GetEnumerator() = (value |> Set.toSeq).GetEnumerator()
        member this.CopyTo(arr, i) = value |> Seq.iteri (fun j o -> arr.SetValue(o, i + j))
        member this.Count = value.Count
        member this.IsReadOnly = false

    member inline this.Value
        with get() = (this :> _ IVariableSet).Value
        and  set v = (this :> _ IVariableSet).Value <- v
    member inline this.Add v = (this :> _ IVariableSet).Add v
    member inline this.Remove v = (this :> _ IVariableSet).Remove v |> ignore
    member inline this.Clear() = (this :> _ IVariableSet).Clear()
    member inline this.Change(removed, added) = (this :> _ IVariableSet).Change(removed, added)

//[<AutoOpen>]
//module private VariableSetHelper =
//    let inline vs x = x :> _ IVariableSet  

type PublishedSet<'a when 'a: comparison>(source: IVariableSet<'a>) as this =
    let collectionChanged = Event<_,_>()
    let ticket = Core.getMappedTicket()

    do  source.AddDependentSet this

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface INotifyCollectionChanged with
        [<CLIEvent>]
        member this.CollectionChanged = collectionChanged.Publish

    interface IVariableSetP<'a> with
        member this.Trigger _ = collectionChanged.Trigger(this, Core.resetCollectionChanged)
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() = source.RemoveDependent this

        member this.TriggerAdd _ v = collectionChanged.Trigger(this, NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, v))
        member this.TriggerRemove _ v = collectionChanged.Trigger(this, NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, v))
        member this.TriggerChange _ (removed, added) = ()
        member this.TriggerClear _ = collectionChanged.Trigger(this, Core.resetCollectionChanged)
                   
    interface IEnumerable with
        member this.GetEnumerator() = (source :> IEnumerable).GetEnumerator()

    interface IVariableSet<'a> with
        member this.Value
            with get() = !source
            and  set v = source := v
        member this.AddDependent p = source.AddDependent p
        member this.RemoveDependent p = source.RemoveDependent p
        member this.AddDependentSet ps = source.AddDependentSet ps
        member this.RemoveDependentSet ps = source.RemoveDependentSet ps
        member this.Add v = source.Add v
        member this.Remove v = source.Remove v
        member this.Change(removed, added) = source.Change(removed, added)
        member this.Clear() = source.Clear()
        member this.Contains(v) = source.Contains v
        member this.GetEnumerator() = source.GetEnumerator()
        member this.CopyTo(arr, i) = source.CopyTo(arr, i)
        member this.Count = source.Count
        member this.IsReadOnly = false

    interface IChangeList with
        member this.CopyTo(arr, i) = !source |> Seq.iteri (fun j o -> arr.SetValue(o, i + j))
        member this.Count = source.Count
        member this.SyncRoot = box this
        member this.IsSynchronized = true
        member this.Item
            with get index = source |> Seq.nth index |> box
            and set index v =
                let old = source |> Seq.nth index
                source.Remove old |> ignore
                source.Add (unbox v)
        member this.Add(v) =
            source.Add (unbox v)
            source |> Seq.tryFindIndex ((=) (unbox v)) |> Option.getOrDefault -1
        member this.Change(removed, added) =
            let sRemoved = if removed <> null then removed |> Seq.cast |> set else Set.empty 
            let sAdded = if added <> null then added |> Seq.cast |> set else Set.empty 
            source.Change(sRemoved, sAdded)                
        member this.Contains(v) = !source |> Set.contains (unbox v)
        member this.Clear() = source.Clear()
        member this.IsReadOnly = false
        member this.IsFixedSize = false
        member this.IndexOf(v) = source |> Seq.tryFindIndex ((=) (unbox v)) |> Option.getOrDefault -1
        member this.Insert(index, v) = source.Add (unbox v)
        member this.Remove(v) = source.Remove (unbox v) |> ignore
        member this.RemoveAt(i) = source.Remove (!source |> Seq.nth i) |> ignore

type VariableSetIntersection<'a when 'a: comparison>(source1: IVariableSet<'a>, source2: IVariableSet<'a>) as this =
    inherit VariableSet<'a>(Set.intersect !source1 !source2)
    let ticket = Core.getMappedTicket()

    do  source1.AddDependentSet this
        source2.AddDependentSet this

    interface IVariableSetP<'a> with
        member this.Trigger _ = ()
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() =
            source1.RemoveDependentSet this       
            source2.RemoveDependentSet this       

        member this.TriggerAdd s v = if (if s === source1 then source2 else source1).Contains v then this.Add v
        member this.TriggerRemove s v = if (this :> _ IVariableSet).Contains v then this.Remove v
        member this.TriggerChange s (removed, added) =
            let thisRemoved = Set.intersect removed !this
            let thisAdded = Set.intersect added (if s === source1 then !source2 else !source1)
            if not thisRemoved.IsEmpty || not thisAdded.IsEmpty then
                this := (!this - thisRemoved + thisAdded)
                        
        member this.TriggerClear _ = if not (!this).IsEmpty then this.Clear()

type VariableSetUnion<'a when 'a: comparison>(source1: IVariableSet<'a>, source2: IVariableSet<'a>) as this =
    inherit VariableSet<'a>(!source1 + !source2)
    let ticket = Core.getMappedTicket()

    do  source1.AddDependentSet this
        source2.AddDependentSet this

    interface IVariableSetP<'a> with
        member this.Trigger _ = ()
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() =
            source1.RemoveDependentSet this       
            source2.RemoveDependentSet this       

        member this.TriggerAdd _ v = if (!this).Contains v |> not then this.Add v
        member this.TriggerRemove s v = if (if s === source1 then source2 else source1).Contains v |> not then this.Remove v
        member this.TriggerChange s (removed, added) =
            let thisRemoved = removed - (if s === source1 then !source2 else !source1)
            let thisAdded = added - !this
            if not thisRemoved.IsEmpty || not thisAdded.IsEmpty then
                this.Change(thisRemoved, thisAdded)
                        
        member this.TriggerClear s =           
            //let otherSet = (if s === source1 then !source2 else !source1)
            let removed = !this - (if s === source1 then !source2 else !source1)
            if not removed.IsEmpty then this.Change(removed, Set.empty)

type VariableSetDifference<'a when 'a: comparison>(source1: IVariableSet<'a>, source2: IVariableSet<'a>) as this =
    inherit VariableSet<'a>(!source1 - !source2)
    let ticket = Core.getMappedTicket()

    do  source1.AddDependentSet this
        source2.AddDependentSet this

    interface IVariableSetP<'a> with
        member this.Trigger _ = ()
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() =
            source1.RemoveDependentSet this       
            source2.RemoveDependentSet this       

        member this.TriggerAdd s v =
            if s === source1 then
                if source2.Contains v |> not then this.Add v
            elif this.Value.Contains v then this.Remove v
        member this.TriggerRemove s v =
            if s === source1 then
                if this.Value.Contains v then this.Remove v
            elif source1.Contains v then this.Add v

        member this.TriggerChange s (removed, added) =
            let thisRemoved, thisAdded =
                if s === source1 then
                    Set.intersect removed this.Value, added - !source2
                else Set.intersect added this.Value, Set.intersect removed !source1
            if not thisRemoved.IsEmpty || not thisAdded.IsEmpty then
                this.Change(thisRemoved, thisAdded)
                        
        member this.TriggerClear s =    
            if s === source1 then
                if this.Value.IsEmpty |> not then this.Clear()
            else
                let added = Set.intersect !source1 !source2
                if added.IsEmpty |> not then this.Change(Set.empty, added)

type MappedSetAction<'a when 'a: comparison>(source: IVariableSet<'a>, onAdded: 'a -> unit, onRemoved: 'a -> unit, onCleared: unit -> unit) as this =
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    do  source.AddDependentSet this

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IVariableSetP<'a> with
        member this.Trigger _ = ()
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() = source.RemoveDependentSet this       
        member this.TriggerAdd _ v = onAdded v
        member this.TriggerRemove s v = onRemoved v
        member this.TriggerChange _ (removed, added) = 
            removed |> Set.iter onRemoved
            added |> Set.iter onAdded                       
        member this.TriggerClear _ = onCleared()

type FilteredSet<'a when 'a: comparison>(source: IVariableSet<'a>, filter: 'a -> bool) as this =
    inherit VariableSet<'a>(!source |> Set.filter filter)
    let ticket = Core.getMappedTicket()

    do  source.AddDependentSet this

    interface IVariableSetP<'a> with
        member this.Trigger _ = ()
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Ticket = ticket
        member this.Dispose() = source.RemoveDependentSet this       

        member this.TriggerAdd _ v = if filter v then this.Add v
        member this.TriggerRemove _ v = if this.Value.Contains v then this.Remove v
        member this.TriggerChange s (removed, added) = 
            let thisRemoved = Set.intersect removed this.Value
            let thisAdded = added |> Set.filter filter
            if not thisRemoved.IsEmpty || not thisAdded.IsEmpty then
                this.Change(thisRemoved, thisAdded)
                        
        member this.TriggerClear s = if not this.Value.IsEmpty then this.Clear()

type MappedVar<'a> =
    | Created of IVariableD<'a>
    | Existing of IVariable<'a>
    member this.AddDependent d = match this with Created v -> v.AddDependent d | Existing v -> v.AddDependent d
    member this.RemoveDependent d = match this with Created v -> v.RemoveDependent d | Existing v -> v.RemoveDependent d
    member this.Value = match this with Created v -> !v | Existing v -> !v

type MappedSet<'a, 'b, 'c when 'a: comparison>(source: IVariableSet<'a>, mapping: 'a -> MappedVar<'b>, collect: seq<'b> -> 'c, level) as this =
    let mutable map = source |> Map.byVal mapping
    let mutable value = Unchecked.defaultof<'c>
    let mutable valueExpired = true
    let mutable needsRefresh = false
    let mutable ticket = Core.getCompTicket level ()
    let mutable dependents = Set.empty<IVariableP>
    
    do  for mv in map |> Map.values do mv.AddDependent this
        source.AddDependentSet this

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IDisposable with
        member this.Dispose() =
            for m in map |> Map.values do
                m.RemoveDependent this
                match m with | Created mv -> dispose mv | _ -> ()         
            source.RemoveDependentSet this
            Core.removeFromRefresh this

    interface IMappedSet<'a> with
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket

        member this.TriggerAdd _ v =
            let m = mapping v  
            m.AddDependent this
            map <- map |> Map.add v m
            Core.addToRefresh this

        member this.TriggerRemove _ v =
            let m = map |> Map.find v
            map <- map |> Map.remove v
            m.RemoveDependent this
            match m with Created mv -> dispose mv | _ -> ()
            Core.addToRefresh this

        member this.TriggerChange _ (removed, added) =
            for r in removed do
                let m = map |> Map.find r
                map <- map |> Map.remove r
                m.RemoveDependent this
                match m with Created mv -> dispose mv | _ -> ()
            for a in added do
                let m = mapping  a  
                m.AddDependent this
                map <- map |> Map.add a m
            Core.addToRefresh this

        member this.TriggerClear _ =
            for m in map |> Map.values do
                m.RemoveDependent this
                match m with Created mv -> dispose mv | _ -> ()
            map <- Map.empty
            Core.addToRefresh this

        member this.Ticket = ticket

        member this.Trigger _ = 
            if not needsRefresh then 
                needsRefresh <- true
                valueExpired <- true
                Core.addToRefresh this

        member this.Refresh() =
            needsRefresh <- false
            for p in dependents do p.Trigger this

    interface IVariableD<'c> with
        member this.Value
            with get() =
                if valueExpired then
                    value <- map |> Map.values |> Seq.map (!) |> collect
                    valueExpired <- false
                value
            and  set v =
                if value != v then
                    value <- v
                    valueExpired <- false
                    for p in dependents do p.Trigger this
                    Core.refresh()

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p

type ReducedSet<'a, 'b when 'a: comparison>(source: IVariableSet<'a>, initVal: 'b, onAdded: 'b -> 'a -> 'b, onRemoved: 'b -> 'a -> 'b) as this =
    let mutable value = initVal
    let mutable dependents = Set.empty
    let ticket = Core.getMappedTicket()

    do  source.AddDependentSet this

    member private this.SetValue v =
        value <- v
        for (p: IVariableP) in dependents do p.Trigger this
        Core.refresh()

    override this.Equals o = this === o
    override this.GetHashCode() = hash ticket

    interface IVariableP with
        member this.Trigger _ = ()
        member this.Ticket = ticket
        member this.CompareTo o = compare ticket (o :?> IVariableP).Ticket 
        member this.Dispose() = source.RemoveDependentSet this 

    interface IVariableP<'b> with
        member this.Value
            with get() = value
            and  set v = if value != v then this.SetValue v

        member this.AddDependent p = dependents <- dependents.Add p
        member this.RemoveDependent p = dependents <- dependents.Remove p

    interface IVariableSetP<'a> with
        member this.TriggerAdd _ v = this.SetValue (onAdded value v)
        member this.TriggerRemove s v = this.SetValue (onRemoved value v)
        member this.TriggerChange _ (removed, added) =            
            this.SetValue (
                value
                |> fun v -> Set.fold onRemoved v removed
                |> fun v -> Set.fold onAdded v added               
            )
        member this.TriggerClear _ = this.SetValue initVal